以下是今天的練習目標:
基於前一篇文的基礎,我們可以畫出一個相機
import sys
import numpy as np
from PIL import Image
from vispy import app, scene, visuals
# Create canvas
canvas = scene.SceneCanvas(title="vispy tutorial", keys="interactive", show=True)
# Make color white
canvas.bgcolor = "white"
# Create view and set the viewing camera
view = canvas.central_widget.add_view()
view.camera = "turntable"
view.camera.fov = 50
view.camera.distance = 10
def create_frustum(aspect_ratio=1.3, camera_to_world=np.eye(4)):
objects = [] # Record all the objects to created in this function
center = np.array([0, 0, 0])
points = np.array([
[0.5, 0.5, 1],
[0.5, -0.5, 1],
[-0.5, -0.5, 1],
[-0.5, 0.5, 1],
])
points[:, 0] *= aspect_ratio
for i in range(4):
line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
objects.append(line)
line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
objects.append(line)
camera_axis = scene.visuals.XYZAxis(parent=view.scene, width=2, antialias=True)
objects.append(camera_axis)
# Create the semi-transparent plane
plane = scene.visuals.Polygon(pos=points, color=(1, 0, 0, 0.5), parent=view.scene)
# Here the z-axis of the plane is ignored, so we need to translate it
plane.transform = scene.transforms.MatrixTransform()
plane.transform.translate([0, 0, 1])
objects.append(plane)
create_frustum()
world_axis = scene.visuals.XYZAxis(parent=view.scene, width=2, antialias=True)
if __name__ == "__main__":
if sys.flags.interactive != 1:
app.run()
不同的是,我們用一個objects
搜集所有我們創建的物件,這樣我們就可以在之後方便的對他們進行操作,並且加入了camera_to_world=np.eye(4)
一個參數,等等會使用到。
接著我們在create_frustum
的最後面加上camera_to_world
的轉換,這樣我們就可以將相機移動到指定的位置。要注意到,在 vispy 的轉換矩陣定義是反過來的,所以我們需要將矩陣轉置一下,再乘上物件原本的轉換。
new_transform = scene.transforms.MatrixTransform()
new_transform.matrix = camera_to_world.T # NOTE: we need to transpose the matrix
for object in objects:
object.transform = new_transform * object.transform
舉例來說,我們可以將相機移動到 (3, 2, 1) 的位置
camera_to_world = np.eye(4)
camera_to_world[0, 3] = 3.0
camera_to_world[1, 3] = 2.0
camera_to_world[2, 3] = 1.0
create_frustum(camera_to_world=camera_to_world)
也可以將相機轉向,延著 x 軸轉 45 度
camera_to_world = np.eye(4)
# Rotate the camera along x-axis for 100 degrees
camera_to_world[:3, :3] = np.array([
[1, 0, 0],
[0, np.cos(np.radians(100)), -np.sin(np.radians(100))],
[0, np.sin(np.radians(100)), np.cos(np.radians(100))]
])
# Translate the camera to (3, 2, 1)
camera_to_world[0, 3] = 3.0
camera_to_world[1, 3] = 2.0
camera_to_world[2, 3] = 1.0
create_frustum(camera_to_world=camera_to_world)
要記得這裡的定義是,先旋轉再平移,所以我們先將相機轉向,再將相機移動到指定的位置。
我們可以任意的組合這些旋轉和平移的矩陣,只要把這些矩陣依次從右到左相乘,就可以得到我們想要的結果。舉例來說,我們可以將相機沿著 x, y, z 軸分別旋轉 100, 30, 300 度,然後平移到 (3, 2, 1) 的位置。利用齊次座標的優點,我們可以將這些操作合併成一個矩陣,再套用到相機上,打出一套組合拳。
# Rotate the camera along x-axis for 100 degrees
rotation1 = np.array([
[1, 0, 0, 0],
[0, np.cos(np.radians(100)), -np.sin(np.radians(100)), 0],
[0, np.sin(np.radians(100)), np.cos(np.radians(100)), 0],
[0, 0, 0, 1]
])
# Rotate the camera along y-axis for 30 degrees
rotation2 = np.array([
[np.cos(np.radians(30)), 0, np.sin(np.radians(30)), 0],
[0, 1, 0, 0],
[-np.sin(np.radians(30)), 0, np.cos(np.radians(30)), 0],
[0, 0, 0, 1]
])
# Rotate the camera along z-axis for 300 degrees
rotation3 = np.array([
[np.cos(np.radians(300)), -np.sin(np.radians(300)), 0, 0],
[np.sin(np.radians(300)), np.cos(np.radians(300)), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
# Translate the camera to (3, 2, 1)
translation = np.array([
[1, 0, 0, 3],
[0, 1, 0, 2],
[0, 0, 1, 1],
[0, 0, 0, 1]
])
# "@" is the matrix multiplication operator in numpy
camera_to_world = translation @ rotation3 @ rotation2 @ rotation1
create_frustum(camera_to_world=camera_to_world)